﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Net;
using System.Xml.Serialization;
using System.Collections.Specialized;
using System.IO;
using Microsoft.Win32;
using System.Security.Permissions;

namespace Avanade.ViddlerDotNet
{
    class ViddlerRequest
    {
        private readonly string baseUrl = string.Empty;
        private readonly string apiKey = string.Empty;
        private string sessionID;
        private string method;
        private Dictionary<string, object> parameters;
        private HttpWebRequest request;

        public string SessionID
        {
            get { return sessionID; }
            set { sessionID = value; }
        }

        public string Method
        {
            get { return method; }
            set { method = value; }
        }

        public Dictionary<string, object> Parameters
        {
            get { return parameters; }
            set { parameters = value; }
        }

        public ViddlerRequest(string method, string apiKey, string baseUrl)
        {
            this.method = method;
            this.apiKey = apiKey;
            this.baseUrl = baseUrl;

            parameters = new Dictionary<string, object>();
        }

        public ViddlerRequest(string method, string sessionID, string apiKey, string baseUrl) : this(method, apiKey, baseUrl)
        {
            parameters.Add(sessionID, sessionID);
        }

        public ViddlerResponse GetResponse()
        {
            PostData();

            return EndRequest();
        }

        public ViddlerResponse UploadFile(string fileName, byte[] data)
        {
            IList<object> prms = new List<object>();

            foreach (string key in parameters.Keys)
            {
                prms.Add(key);
                prms.Add(parameters[key]);
            }

            object [] args = new object[prms.Count];

            prms.CopyTo(args, 0);

            return Upload(fileName, data, args);
        }

        private static void EncodeAndAddItem(string boundary, ref StringBuilder baseRequest, params object[] items)
        {
            if (baseRequest == null)
            {
                baseRequest = new StringBuilder();
            }

            const string form_data = "Content-Disposition: form-data; name=\"{0}\"\r\n";
            const string photo_key = "Content-Disposition: form-data; name=\"{0}\";filename=\"{1}\"\r\n";
            const string escape = "\r\n";
            const string content_type = "Content-Type:application/octet-stream";
            const string dash = "--";

            for (int index = 0; index < items.Length; index += 2)
            {
                string key = Convert.ToString(items[index]);
                string value = string.Empty;

                if (index + 1 < items.Length)
                {
                    value = Convert.ToString(items[index + 1]);
                }

                if (!string.IsNullOrEmpty(value))
                {
                    baseRequest.Append(dash);
                    baseRequest.Append(boundary);
                    baseRequest.Append(escape);

                    if (string.Compare(key, "file", true) == 0)
                    {
                        baseRequest.Append(string.Format(photo_key, key, value));
                        baseRequest.Append(content_type);
                        baseRequest.Append(escape);
                        baseRequest.Append(escape);
                    }
                    else
                    {
                        baseRequest.Append(string.Format(form_data, key));
                        baseRequest.Append(escape);
                        baseRequest.Append(value);
                        baseRequest.Append(escape);
                    }
                }
            }

        }

        private ViddlerResponse Upload(string fileName, byte[] data, object[] args)
        {
            string boundary = "-------------------------" + DateTime.Now.Ticks.ToString("x"); ;

            StringBuilder builder = new StringBuilder();

            EncodeAndAddItem(boundary, ref builder, new object[] { "api_key", apiKey, "sessionid", sessionID, "method", method});
            EncodeAndAddItem(boundary, ref builder, args);
            EncodeAndAddItem(boundary, ref builder, new object[] { "file", fileName });

            //builder = builder.Remove(builder.Length - 4, 4);

            // Create a request using a URL that can receive a post. 
            HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(baseUrl);
            // Set the Method property of the request to POST.
            httpWebRequest.Method = "POST";
            httpWebRequest.KeepAlive = true;
            // never timeout.
            httpWebRequest.Timeout = 300000;
            // Set the ContentType property of the WebRequest.
            httpWebRequest.ContentType = "multipart/form-data; boundary=" + boundary;

            byte[] photoAttributeData = Encoding.ASCII.GetBytes(builder.ToString());
            byte[] footer = Encoding.ASCII.GetBytes("\r\n--" + boundary + "--\r\n");

            byte[] postContent = new byte[data.Length + photoAttributeData.Length + footer.Length];

            Buffer.BlockCopy(photoAttributeData, 0, postContent, 0, photoAttributeData.Length);
            Buffer.BlockCopy(data, 0, postContent, photoAttributeData.Length, data.Length);
            Buffer.BlockCopy(footer, 0, postContent, data.Length + photoAttributeData.Length, footer.Length);

            // Set the ContentLength property of the WebRequest.
            httpWebRequest.ContentLength = postContent.Length;
            // Get the request stream.
            Stream dataStream = httpWebRequest.GetRequestStream();

            dataStream.Write(postContent, 0, postContent.Length);
            // Close the Stream object.
            dataStream.Flush();
            dataStream.Close();
            // Get the response.
            WebResponse response = httpWebRequest.GetResponse();
            // Get the stream containing content returned by the server.
            dataStream = response.GetResponseStream();

            //// Open the stream using a StreamReader for easy access.
            //StreamReader reader = new StreamReader(dataStream);
            //// Read the content.
            //string responseFromServer = reader.ReadToEnd();
            //// Clean up the streams.
           
            ViddlerResponse viddlerResponse = new ViddlerResponse(dataStream);

            dataStream.Close();
            response.Close();

            if (viddlerResponse.MessageType == "error")
            {
                throw ViddlerException.GetFromErrorObject(viddlerResponse.GetResponseObject<Error>());
            }
            else
            {
                return viddlerResponse;
            }
        }

        private void PostData()
        {
            // Construct query string collection
            NameValueCollection queryString = new NameValueCollection();
            queryString.Add("api_key", apiKey);
            queryString.Add("method", method);

            if (SessionID != null)
            {
                queryString.Add("sessionid", sessionID);
            }

            foreach (string key in parameters.Keys)
            {
                queryString.Add(key, parameters[key].ToString());
            }

            // Create a boundry
            string boundary = "-------------------------" + DateTime.Now.Ticks.ToString("x");

            // Create a request for the URL.
            request = (HttpWebRequest)WebRequest.Create(baseUrl);
            //request.Headers.Add("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 1.0.3705;)");
            request.ContentType = "multipart/form-data; boundary=" + boundary;
            request.Method = "POST";
            request.KeepAlive = true;
            //request.Proxy.Credentials = new NetworkCredential("soschuttens", "@vanade04", "europe");

            // If required by the server, set the credentials.
            request.Credentials = System.Net.CredentialCache.DefaultCredentials;

            // Get the boundry in bytes
            byte[] boundaryBytes = Encoding.ASCII.GetBytes("\r\n--" + boundary + "\r\n");
            byte[] endBoundaryBytes = Encoding.ASCII.GetBytes("\r\n--" + boundary + "--\r\n");
            byte[] fileHeaderBytes = new byte[0];
            FileStream fileStream = null;

            List<byte> content = new List<byte>();

            // Add query string bytes
            Dictionary<string, byte[]> queryStringBytes = new Dictionary<string, byte[]>();
            foreach (string key in queryString.Keys)
            {
                string queryData = String.Format(
                    "Content-Disposition: form-data; name=\"{0}\"\r\n\r\n{1}",
                    key, queryString[key]);

                // Add boundary
                content.AddRange(boundaryBytes);

                // Add content
                content.AddRange(Encoding.ASCII.GetBytes(queryData));
            }

            // Add all of the content up
            request.ContentLength = content.Count + endBoundaryBytes.Length;

            // Get the output stream
            Stream requestStream = request.GetRequestStream();

            // Write content
            requestStream.Write(content.ToArray(), 0, content.Count);
            // Write end boundary
            requestStream.Write(endBoundaryBytes, 0, endBoundaryBytes.Length);

            // Close the request stream
            requestStream.Close();
        }

        private ViddlerResponse EndRequest()
        {
            WebResponse webResponse = request.GetResponse();

            Stream responseStream = webResponse.GetResponseStream();

            // Deserialize the response
            ViddlerResponse response = new ViddlerResponse(responseStream);

            // Close the stream
            responseStream.Close();

            // Close response object.
            webResponse.Close();

            if (response.MessageType == "error")
            {
                throw ViddlerException.GetFromErrorObject(response.GetResponseObject<Error>());
            }
            else
            {
                return response;
            }
        }
    }
}
